﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using __ts.Core.Api;
using log4net;

/*
 *  author: able
 *  @2012/2/21 10:38:07
 * 
 */
namespace __ts.Core.Filter.Codec
{
public class ProtocolCodecFilter : DefaultIoFilter {

    /** A logger for this class */
    private static ILog LOGGER = LogManager.GetLogger(typeof(ProtocolCodecFilter));

    //private static Class<?>[] EMPTY_PARAMS = new Class[0];

    /** key for session attribute holding the encoder */
    //private AttributeKey<ProtocolEncoder> ENCODER = new AttributeKey<ProtocolEncoder>(ProtocolEncoder.class,"internal_encoder");

    ///** key for session attribute holding the decoder */
    //private AttributeKey<ProtocolDecoder> DECODER = new AttributeKey<ProtocolDecoder>(ProtocolDecoder.class,"internal_decoder");

    /** The factory responsible for creating the encoder and decoder */
    private ProtocolCodecFactory factory;

    /**
     * 
     * Creates a new instance of ProtocolCodecFilter, associating a factory
     * for the creation of the encoder and decoder.
     *
     * @param factory The associated factory
     */
    public ProtocolCodecFilter(ProtocolCodecFactory factory) {
        if (factory == null) {
            throw new ArgumentNullException("factory");
        }

        this.factory = factory;
    }

    /**
     * Creates a new instance of ProtocolCodecFilter, without any factory.
     * The encoder/decoder factory will be created as an inner class, using
     * the two parameters (encoder and decoder).
     * 
     * @param encoder The class responsible for encoding the message
     * @param decoder The class responsible for decoding the message
     */
    public ProtocolCodecFilter(ProtocolEncoder encoder, ProtocolDecoder decoder) {
        if (encoder == null) {
            throw new ArgumentNullException("encoder");
        }

        if (decoder == null) {
            throw new ArgumentNullException("decoder");
        }

        // Create the inner Factory based on the two parameters
        //this.factory = new ProtocolCodecFactory() {
        //    public ProtocolEncoder getEncoder(IoSession session) {
        //        return encoder;
        //    }

        //    public ProtocolDecoder getDecoder(IoSession session) {
        //        return decoder;
        //    }
        //};
    }

    /**
     * Creates a new instance of ProtocolCodecFilter, without any factory.
     * The encoder/decoder factory will be created as an anonymous class, using
     * the two parameters (encoder and decoder), which are class names. Instances
     * for those classes will be created in this constructor.
     * 
     * @param encoder The class responsible for encoding the message
     * @param decoder The class responsible for decoding the message
     */
    public ProtocolCodecFilter(ProtocolEncoder encoderClass, ProtocolDecoder decoderClass) {
        if (encoderClass == null) {
            throw new ArgumentNullException("encoderClass");
        }

        if (decoderClass == null) {
            throw new ArgumentNullException("decoderClass");
        }

        if (!typeof(ProtocolEncoder).IsAssignableFrom(encoderClass.GetType())) {
            throw new ArgumentNullException("encoderClass: " + encoderClass);
        }

        if (!typeof(ProtocolDecoder).IsAssignableFrom(decoderClass.GetType())) {
            throw new ArgumentNullException("decoderClass: " + decoderClass);
        }

        try {
            //encoderClass.getConstructor(EMPTY_PARAMS);
        } catch (Exception e) {
            throw new ArgumentNullException("encoderClass doesn't have a public default constructor.");
        }

        try {
            //decoderClass.getConstructor(EMPTY_PARAMS);
        } catch (Exception e) {
            throw new ArgumentNullException("decoderClass doesn't have a public default constructor.");
        }

        ProtocolEncoder encoder;

        try {
            encoder = Activator.CreateInstance<ProtocolEncoder>();
        } catch (Exception e) {
            throw new ArgumentNullException("encoderClass cannot be initialized");
        }

        ProtocolDecoder decoder;

        try {
            decoder = Activator.CreateInstance<ProtocolDecoder>();
        } catch (Exception e) {
            throw new ArgumentNullException("decoderClass cannot be initialized");
        }

        // Create the inner factory based on the two parameters.
        //this.factory = new ProtocolCodecFactory() {
        //    public ProtocolEncoder getEncoder(IoSession session) {
        //        return encoder;
        //    }

        //    public ProtocolDecoder getDecoder(IoSession session) {
        //        return decoder;
        //    }
        //};
    }

    /**
     * Get the encoder instance from a given session.
     *
     * @param session The associated session we will get the encoder from
     * @return The encoder instance, if any
     */
    public ProtocolEncoder getEncoder(IoSession session) {
        return factory.getEncoder(session);
    }

    /**
     * Get the decoder instance from a given session.
     *
     * @param session The associated session we will get the decoder from
     * @return The decoder instance, if any
     */
    public ProtocolDecoder getDecoder(IoSession session) {
        return factory.getDecoder(session);
    }

    /**
     * Process the incoming message, calling the session decoder. As the incoming
     * buffer might contains more than one messages, we have to loop until the decoder
     * throws an exception.
     * <code>
     *  while ( buffer not empty )
     *    try
     *      decode ( buffer )
     *    catch
     *      break;
     * </code>
     */
    @Override
    public void messageReceived(IoSession session, Object message, ReadFilterChainController controller) {
        LOGGER.debug("Processing a MESSAGE_RECEIVED for session {}", session);

        if (!(message instanceof ByteBuffer)) {
            controller.callReadNextFilter(message);
            return;
        }

        ByteBuffer in = (ByteBuffer) message;
        ProtocolDecoder decoder = getDecoder(session);

        // Loop until we don't have anymore byte in the buffer,
        // or until the decoder throws an unrecoverable exception or
        // can't decoder a message, because there are not enough
        // data in the buffer
        while (in.hasRemaining()) {
            // Call the decoder with the read bytes
            decoder.decode(session, in, controller);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void messageWriting(IoSession session, Object message, WriteFilterChainController controller) {
        LOGGER.debug("Processing a MESSAGE_WRITTING for session {}", session);

        ProtocolEncoder encoder = session.getAttribute(ENCODER, null);

        encoder.encode(session, message, controller);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void sessionCreated(IoSession session) {
        // Initialize the encoder and decoder if we use a factory
        if (factory != null) {
            ProtocolEncoder encoder = factory.getEncoder(session);
            session.setAttribute(ENCODER, encoder);
            ProtocolDecoder decoder = factory.getDecoder(session);
            session.setAttribute(DECODER, decoder);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void sessionClosed(IoSession session) {
        disposeCodec(session);
    }

    //----------- Helper methods ---------------------------------------------
    /**
     * Dispose the encoder, decoder, and the callback for the decoded
     * messages.
     */
    private void disposeCodec(IoSession session) {
        // We just remove the two instances of encoder/decoder to release resources
        // from the session
        disposeEncoder(session);
        disposeDecoder(session);
    }

    /**
     * Dispose the encoder, removing its instance from the
     * session's attributes, and calling the associated
     * dispose method.
     */
    private void disposeEncoder(IoSession session) {
        ProtocolEncoder encoder = (ProtocolEncoder) session.removeAttribute(ENCODER);

        if (encoder == null) {
            return;
        }

        try {
            encoder.dispose(session);
        } catch (Throwable t) {
            LOGGER.warn("Failed to dispose: " + encoder.getClass().getName() + " (" + encoder + ')');
        }
    }

    /**
     * Dispose the decoder, removing its instance from the
     * session's attributes, and calling the associated
     * dispose method.
     */
    private void disposeDecoder(IoSession session) {
        ProtocolDecoder decoder = (ProtocolDecoder) session.removeAttribute(DECODER);
        if (decoder == null) {
            return;
        }

        try {
            decoder.dispose(session);
        } catch (Throwable t) {
            LOGGER.warn("Failed to dispose: " + decoder.getClass().getName() + " (" + decoder + ')');
        }
    }
}
}